package com.magic.cd.util;

import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.magic.cd.core.MiniCatPacket;


/**
 * MiCatUtils 提供模拟登录的相关工具
 * */
public class MiniCatUtils {
	/**
	 * 获取去权能标识,这里只增加我们需要的权能标识,
	 * 实际场景需要通过密码有无、需要连接数据库来做具体的判断
	 * 
	 * */
	public static int getCapability(){
		int cap = 0;
		cap |= Const.CLIENT_LONG_FLAG;
		cap |= Const.CLIENT_FOUND_ROWS;
		cap |= Const.CLIENT_PLUGIN_AUTH;
		cap |= Const.CLIENT_LOCAL_FILES;
		cap |= Const.CLIENT_PROTOCOL_41;
		cap |= Const.CLIENT_TRANSACTIONS;
		cap |= Const.CLIENT_MULTI_RESULTS;
		cap |= Const.CLIENT_LONG_PASSWORD;
		cap |= Const.CLIENT_CONNECT_WITH_DB;
		cap |= Const.CLIENT_SECURE_CONNECTION;
		return cap;
	}
	
	/**
	 * 登录MySQL时将握手的seed和密码进行加密混淆
	 * @param	pwd		密码
	 * @param	seed    握手包的挑战随机数
	 * @param   fromServer  握手包客户端收到的数据
	 * @param	toServer    将要发送出去的数据
	 * */
	public static void maxturePwd(String pwd,String seed,MiniCatPacket fromServer,List<MiniCatPacket> toServer){
		try {
			toServer.clear();
			MiniCatPacket bresp = null;
			if (fromServer == null || pwd == null || pwd.length() == 0) {
				bresp = new MiniCatPacket(new byte[0]);
			} else {
				bresp = new MiniCatPacket(MiniCatUtils.scramble411(pwd,seed,null));
			}
			toServer.add(bresp);
			
		} catch (Exception e) {
			throw new RuntimeException(e.getMessage());
		}
	}
	
	/**
	 * 混淆密码
	 * */
	private static byte[] scramble411(String password, String seed, String passwordEncoding)
			throws NoSuchAlgorithmException, UnsupportedEncodingException {
		MessageDigest md = MessageDigest.getInstance("SHA-1"); 
		
		byte[] psdByte = (passwordEncoding==null||passwordEncoding.length()==0)? 
				MiniCatUtils.getBytes(password):MiniCatUtils.getBytes(password, passwordEncoding);
		
		byte[] passwordHashStage1 = md.digest(MiniCatUtils.read(psdByte, 0, psdByte.length-1));
		md.reset();

		byte[] passwordHashStage2 = md.digest(passwordHashStage1);
		md.reset();

		byte[] seedAsBytes = MiniCatUtils.getBytes(seed, "ASCII"); 
		
		seedAsBytes = MiniCatUtils.read(seedAsBytes, 0, seedAsBytes.length-1);
		
		md.update(seedAsBytes);
		md.update(passwordHashStage2);
		byte[] toBeXord = md.digest();
		int numToXor = toBeXord.length;
		for (int i = 0; i < numToXor; i++) {
			toBeXord[i] = (byte) (toBeXord[i] ^ passwordHashStage1[i]);
		}
		return toBeXord;
	}
	
	/**
	 * 从字符串中读取byte[]
	 * 按MySQL协议需要增加一位0
	 * */
	public static byte[] getBytes(String value) {
		return getBytes(value,"UTF-8");
	}
	
	/**
	 * 从字符串中读取byte[]
	 * 按MySQL协议需要增加一位0
	 * */
	public static byte[] getBytes(String value,String encoding) {
		try {
			Charset cs = Charset.forName(encoding);
			ByteBuffer buf = cs.encode(value);
			int encodedLen = buf.limit()+1;
			byte[] asBytes = new byte[encodedLen];
			buf.get(asBytes, 0, encodedLen-1);
			return asBytes;
		} catch (Exception e) {
			throw new RuntimeException(e.getMessage());
		}
	}
	
	/**
	 * 将从MySQL端收到的数据解析成MiniCatPacket
	 * */
	public static MiniCatPacket parsePacket(byte[] origin){
		int len = origin.length-4;
		if(len<=0) throw new RuntimeException("MYSQL服务器连接失败！");
		byte[] newPkg = new byte[len];
		System.arraycopy(origin, 4, newPkg, 0,len);
		return new MiniCatPacket(newPkg);
	}
	
	/**
	 * 读取byte数组
	 * start 从0开始
	 * len 为 -1时表示全部
	 * */
	public static byte[] read(byte[] old,int start,int len){
		if(len==-1) len = old.length-start;
		byte[] bb = new byte[len];
		System.arraycopy(old, start, bb, 0, len);
		return bb;
	}
	
	/**
	 * 拼接两个byte数组
	 * src  父byte[]
	 * dest 子byte[]
	 * */
	public static byte[] append(byte[] src,byte[] dest){
		int srcLen = src.length,destLen = dest.length;
		
		byte[] bb = new byte[srcLen+destLen];
		System.arraycopy(src, 0, bb, 0, srcLen);
		System.arraycopy(dest,0,bb,srcLen,destLen);
		return bb;
	}
	
	//----------------------解析握手认证包-----------------------
	/**
	 * 按协议顺序解析握手认证包
	 * @param	pkg    握手包
	 * @return	Map    解析结果
	 * */
	public static Map<String,Object> parseShakePkg(MiniCatPacket pkg){
		Map<String,Object> res = new HashMap<String,Object>();
		byte pVer = pkg.readByte();
		String sVer = pkg.readString("ASCII");
		
		long tId = pkg.readLong();
		String seed1 = pkg.readString("ASCII");
		
		int sCap = pkg.readInt();
		int sChar = pkg.readByte()&0xff;
		int sStatus = pkg.readInt();
		sCap|=pkg.readInt()<<16;
		int len = pkg.readByte() & 0xff;
		pkg.setSkipIndex(10);
		String seed2 = pkg.readString("ASCII",len-8);
		
		res.put("protocolVersion", pVer);
		res.put("serverVersion", sVer);
		res.put("threadId", tId);
		res.put("capability", sCap);
		res.put("charset", sChar);
		res.put("packetLength", len);
		res.put("serverStatus", sStatus);
		res.put("seed", (seed1.trim()+seed2.trim()));

		return res;
	}
	
	/**
	 * 组装登录packet
	 * @param	user	用户名
	 * @param   pwd     密码
	 * @param   dbs     需要连接的数据库
	 * @param	seed	服务器传来的挑战随机数
	 * @return	MiniCatPacket
	 * */
	public static MiniCatPacket loginPkg(String user,String pwd,String dbs,String seed){
		int passwordLength = 16;
		int userLength = (user != null) ? user.length() : 0;
		int databaseLength = (dbs != null) ? dbs.length() : 0;
		int packLength = ((userLength+passwordLength+databaseLength)*3)+7+4+33;
		
		MiniCatPacket send = new MiniCatPacket(packLength);
		
		send.writeLong(MiniCatUtils.getCapability());
		
		int maxThreeBytes = (256 * 256 * 256) - 1;
		send.writeLong(maxThreeBytes);
		
		int UTF_8CHARSET = 33;
		send.writeByte((byte) UTF_8CHARSET);
		send.writeBytesNoNull(new byte[23]);	
		
		//user
		send.writeString(user);
		
		//pwd
		MiniCatPacket fromServer = new MiniCatPacket(MiniCatUtils.getBytes(seed));
		ArrayList<MiniCatPacket> toServer = new ArrayList<MiniCatPacket>();
		MiniCatUtils.maxturePwd(pwd, seed, fromServer, toServer);
		
		send.writeByte((byte) toServer.get(0).getMaxLength());
		send.writeBytesNoNull(toServer.get(0).getBytePacket());
		
		//dbs
		send.writeString(dbs);
		
		//mysql_native_password
		send.writeString("mysql_native_password");
		
		return send;
	}
}
